﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Net.Forums
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Threading;

    using Phuse.Mime;
    using Phuse.Net.Nntp;

    /// <summary>Provides methods to send/receive articles to/from an NNTP host.</summary>
    /// <threadsafety static="true" instance="false"/>
    public sealed class NntpForumProvider : IForumProvider<NntpConnection>
    {
        /// <summary>See <see cref="IForumProvider{T}.Connect"/>.</summary>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "Method is not supposed to be called by client code.")]
        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The caller is responsible for disposal.")]
        public NntpConnection Connect(IHostInfo host, CancellationToken cancellationToken)
        {
            using (var clientGuard = ScopeGuard.Create(new Client(host.Name, host.Port, cancellationToken)))
            using (var resultGuard = ScopeGuard.Create(new NntpConnection(host, clientGuard.Resource)))
            {
                var accountName = host.AccountName;
                var password = host.Password;

                if (!string.IsNullOrEmpty(accountName))
                {
                    new AuthInfoCommand(accountName, password).Execute(clientGuard.Resource);
                }

                clientGuard.Dismiss();
                resultGuard.Dismiss();
                return resultGuard.Resource;
            }
        }

        /// <summary>See <see cref="IForumProvider{T}.SendArticle(T, Message)"/>.</summary>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "Method is not supposed to be called by client code.")]
        public Exception SendArticle(NntpConnection connection, Message message)
        {
            try
            {
                new PostCommand(stream => message.WriteTo(stream)).Execute(connection.Client);
                return null;
            }
            catch (ClientException ex)
            {
                return ex;
            }
        }

        /// <summary>See <see cref="IForumProvider{T}.GetHighWaterMark(T, string)"/>.</summary>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "Method is not supposed to be called by client code.")]
        public long GetHighWaterMark(NntpConnection connection, string groupName)
        {
            var groupResponse = new GroupCommand(groupName).Execute(connection.Client);

            // Servers might report an empty group in different ways. One way is when the article count is 0 while the
            // low and high water marks might suggest the existence of articles, see
            // http://www.ietf.org/rfc/rfc3977.txt, 6.1.1.2.
            return groupResponse.ArticleCount == 0 ? 0 : groupResponse.HighWaterMark;
        }

        /// <summary>See <see cref="IForumProvider{T}.ReceiveHeaders"/>.</summary>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "Method is not supposed to be called by client code.")]
        public void ReceiveHeaders(
            NntpConnection connection,
            string groupName,
            IInboundArticleInfo lastOldArticle,
            long lastOldArticleNumber,
            SaveHeaderCallback saveHeader,
            SaveContentCallback saveContent)
        {
            using (var overResponse = new OverCommand(lastOldArticleNumber + 1, null).Execute(connection.Client))
            {
                while (overResponse.MoveNext())
                {
                    var info = overResponse.Current;

                    using (var stream = info.HeaderOverview)
                    {
                        var overview = Message.Parse(stream);

                        saveHeader(
                            info.Number,
                            overview.MessageId,
                            overview.Date,
                            overview.FromField == null ? null : overview.FromField.GetBody(),
                            overview.Subject,
                            overview.References);
                    }
                }
            }
        }

        /// <summary>See <see cref="IForumProvider{T}.ReceiveContents"/>.</summary>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "Method is not supposed to be called by client code.")]
        public void ReceiveContents(
            NntpConnection connection,
            string groupName,
            IList<IInboundArticleInfo> contentsToReceive,
            SaveContentCallback saveContent,
            DeleteArticleCallback deleteArticle)
        {
            var client = connection.Client;

            foreach (var article in contentsToReceive)
            {
                client.EnqueueCommand(new ArticleCommand(article.Number));
            }

            foreach (var article in contentsToReceive)
            {
                try
                {
                    using (var response = (ArticleResponse)client.GetResponse())
                    {
                        if (article.Number != response.Number)
                        {
                            throw new UnexpectedResponseException("The server responded with an unexpected article.");
                        }

                        saveContent(article, response.Content);
                    }
                }
                catch (NoSuchArticleException)
                {
                    // This can happen when an article is removed between overview & article receive
                    deleteArticle(article);
                }
            }
        }
    }
}
